/*
 * Copyright (c) 2010-2011 Josef Hardi <josef.hardi@gmail.com>
 * Copyright (c) 2010 Piotr Zagawa <piotr.zagawa@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package id.duapuluhdua.dakidaki.loader;

import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;

import android.graphics.Canvas;
import android.graphics.Rect;

import id.duapuluhdua.dakidaki.MainState;
import id.duapuluhdua.dakidaki.activities.ActivityMain;
import id.duapuluhdua.dakidaki.data.common.DataTable;
import id.duapuluhdua.dakidaki.data.common.DataTableOperation;
import id.duapuluhdua.dakidaki.data.common.Database;
import id.duapuluhdua.dakidaki.gps.GpsLocationStatus;
import id.duapuluhdua.dakidaki.map.Screen;
import id.duapuluhdua.dakidaki.map.Viewport;

/**
 *
 */
public abstract class LoaderItemCollection<T extends LoaderItem> implements Observer
{
  /** Fields */
  protected ActivityMain mParent;
  protected Database mDatabase;
  protected MainState mState;

  protected Screen mScreen;
  protected GpsLocationStatus mGpsLocationStatus;
  protected DataTable mTable;

  protected final Boolean bListAccessMutex = false;
  protected ArrayList<T> mVecItem = new ArrayList<T>();

  public LoaderItemCollection(ActivityMain parent, Screen screen,
      GpsLocationStatus gpsLocationStatus, DataTable table)
  {
    mParent = parent;
    mDatabase = parent.getDatabase();
    mState = parent.getMainState();

    mScreen = screen;
    mGpsLocationStatus = gpsLocationStatus;
    mTable = table;

    // Observe for resetViewPort
    screen.addObserver(this);

    // Observe for location updates
    gpsLocationStatus.addObserver(this);
  }

  @Override
  public void update(Observable observable, Object data)
  {
    // Observe for screen ViewPort updates
    if (observable == mScreen) {
      if (data != Viewport.MODE_FREE) {
        updateForResetViewPort();
      }
    }

    // Observe for new location update
    if (observable == mGpsLocationStatus) {
      if (data == GpsLocationStatus.STATE_NEW_GPS_LOCATION) {
        // Update collection if in the save point mode
        updateForNewLocation(mGpsLocationStatus.isSaveMode());
      }
    }

    // Observe for data table update
    if (observable instanceof DataTable) {
      if (data instanceof DataTableOperation) {
        final DataTableOperation op = (DataTableOperation)data;
        switch (op.operation) {
          case DataTableOperation.OP_INSERT :
          case DataTableOperation.OP_UPDATE :
            loadItem(op.rowId);
            break;
          case DataTableOperation.OP_DELETE :
            removeItem(op.rowId);
            break;
        }
      }
    }
  }

  /**
   *
   */
  public void loadAllItems()
  {
    synchronized(bListAccessMutex)
    {
      mVecItem.clear();
      final ArrayList<Long> list = mTable.getRowsIdList(null, null);
      if (list != null) {
        for (Long itemId : list) {
          loadItem(itemId);
        }
      }
    }
  }

  public ArrayList<Long> getIdList()
  {
    ArrayList<Long> list = new ArrayList<Long>();
    synchronized(bListAccessMutex)
    {
      for (T loaderItem : mVecItem) {
        list.add(loaderItem.getId());
      }
    }
    return list;
  }

  public T getLoaderItem(long itemId)
  {
    T foundLoaderItem = null;
    synchronized(bListAccessMutex)
    {
      for (T loaderItem : mVecItem) {
        if (loaderItem.getId() == itemId) {
          foundLoaderItem = loaderItem;
          break;
        }
      }
    }
    return foundLoaderItem;
  }

  /**
   * Adds an item specified by the item id to the collection.
   *
   * @param itemId the id number.
   */
  protected void loadItem(long itemId)
  {
    if (itemId != -1) {
      synchronized(bListAccessMutex)
      {
        final T loaderItem = getLoaderItem(itemId);
        if (loaderItem == null) {
          // Load item
          final T newLoaderItem = createItem(mParent, mScreen, mDatabase, itemId);
          if (newLoaderItem.reload()) {
            mVecItem.add(newLoaderItem);
            sortItems();
          }
        }
        else {
          // Update item
          loaderItem.reload();
        }
      }
    }
  }

  /**
   * Removes an item as specified by the item id from the collection and then
   * sorts them.
   *
   * @param itemId the id number.
   */
  protected void removeItem(long itemId)
  {
    synchronized(bListAccessMutex)
    {
      for (int index = 0; index < mVecItem.size(); index++) {
        final T loaderItem = mVecItem.get(index);
        if ((loaderItem != null) && (loaderItem.getId() == itemId)) {
          mVecItem.remove(index);
          sortItems();
          break;
        }
      }
    }
  }

  /**
   * Notifies all the layers about the screen size changes due to change
   * in the screen orientation.
   *
   * @param width the new width.
   * @param height the new height.
   * @param screenOrientation the new screen orientation.
   */
  public void surfaceSizeChanged(int width, int height, int screenOrientation)
  {
    synchronized(bListAccessMutex)
    {
      for (T loaderItem : mVecItem) {
        loaderItem.getLayer().surfaceSizeChanged(width, height, screenOrientation);
      }
    }
  }

  /**
   *
   */
  public void updateObjectsState()
  {
    synchronized(bListAccessMutex)
    {
      for (T loaderItem : mVecItem) {
        loaderItem.getLayer().updateObjectsState();
      }
    }
  }

  /**
   *
   * @param canvas
   * @param rtBounds
   * @param selectedItemId
   */
  public void doDraw(Canvas canvas, Rect rtBounds, long selectedItemId)
  {
    synchronized(bListAccessMutex)
    {
      T loaderItemSelected = null;

      for (T loaderItem : mVecItem) {
        if (loaderItem.getId() == selectedItemId) {
          loaderItemSelected = loaderItem;
        }
        else {
          loaderItem.getLayer().doDraw(canvas, rtBounds);
        }
      }
      // Draw selected item last, on top
      if (loaderItemSelected != null) {
        loaderItemSelected.getLayer().doDraw(canvas, rtBounds);
      }
    }
  }

  protected abstract void updateForResetViewPort();

  protected abstract void updateForNewLocation(boolean locationSaved);

  protected abstract void sortItems();

  protected abstract T createItem(ActivityMain parent, Screen screen,
      Database database, long itemId);
}
